﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace IBNet.UtilsDataStructuresAndExtensions
{
   /// <summary>
   /// A fixed-length array-like collection that supports O(1) push-fronts.
   /// </summary>
   /// <remarks>
   /// Fixed-length in that if the internal array is [4 3 2 1 0] and 
   /// 5 is push-fronted, the resulting array will be [5 4 3 2 1].
   /// This push-front takes O(1) time as opposed to O(N) time with 
   /// a push-front to a List&lt;T>.
   /// </remarks>
   public class FixedLengthCollection<T> : IEnumerable<T>
   {
      private readonly T[] _array;
      private int _count;
      private int _indexForPushFront;

      /// <summary>
      /// Creates a new FixedLengthCollection&lt;T>.
      /// </summary>
      /// <param name="length">The fixed number of elements the FixedLengthCollection&lt;T> can hold.</param>
      public FixedLengthCollection(int length)
      {
         if (length < 1)
            throw new ArgumentOutOfRangeException("length", "length must be greater than or equal to 1");

         _array = new T[length];
         _indexForPushFront = -1;
      }

      /// <summary>
      /// Gets the number of elements contained in the FixedLengthCollection&lt;T>.
      /// </summary>
      public int Count { get { return _count; } }

      /// <summary>
      /// Gets the fixed number of elements the FixedLengthCollection&lt;T> can hold.
      /// </summary>
      public int Length { get { return _array.Length; } }
      
      /// <summary>
      /// Adds an element to the start of the FixedLengthCollection&lt;T>, 
      /// which causes the last element of the FixedLengthCollection&lt;T> to disappear.
      /// </summary>
      /// <param name="item">The element to push front to the FixedLengthCollection&lt;T>.</param>
      public void PushFront(T item)
      {
         if (_count < _array.Length)
         {
            _count++;
         }

         _indexForPushFront++;
         if (_indexForPushFront == _array.Length)
         {
            _indexForPushFront = 0;
         }
         _array[_indexForPushFront] = item;
      }

      /// <summary>
      /// Gets an element at a specified index.
      /// </summary>
      /// <param name="index"></param>
      /// <returns>The zero-based index of the element to get.</returns>
      public T this[int index]
      {
         get
         {
            if (index < 0 || index >= _count)
               throw new IndexOutOfRangeException("index");

            int adjustedIndex = _indexForPushFront - index;
            if (adjustedIndex < 0)
            {
               adjustedIndex = adjustedIndex + _array.Length;
            }
            T element = _array[adjustedIndex];
            return element;
         }
      }

      /// <summary>
      /// Returns a ReadOnlyFixedLengthCollection&lt;T> wrapper for this 
      /// FixedLengthCollection&lt;T>.
      /// </summary>
      public ReadOnlyFixedLengthCollection<T> AsReadOnly()
      {
         ReadOnlyFixedLengthCollection<T> readOnlyFixedLengthCollectionWrapper =
            new ReadOnlyFixedLengthCollection<T>(this);
         return readOnlyFixedLengthCollectionWrapper;
      }

      /// <summary>
      /// Returns an enumerator that iterates through the FixedLengthCollection&lt;T>.
      /// </summary>
      public IEnumerator<T> GetEnumerator()
      {
         for (int i = 0; i < _count; i++)
         {
            yield return this[i];
         }
      }

      /// <summary>
      /// Returns an enumerator that iterates through the FixedLengthCollection&lt;T>.
      /// </summary>
      IEnumerator IEnumerable.GetEnumerator()
      {
         for (int i = 0; i < _count; i++)
         {
            yield return this[i];
         }
      }
   }
}
